package com.droidski.taskdb.cli;

/*
********************************************************************************
n 1  ( ) Projects 
n 10  ( ) confirm moving based on child size 
n 11 (1) Tasks 
n 12 (1) Report class passing in header then array of strings 
n 13 (1) priority 
n 14 (1) serialisation using version numbers 
n 15 (1) configuration 
n 16 (1) import from excel 
n 17 (1) export to excel 
n 18 (1) export to XML 
n 19 (1) reporting 
n 20 (1) task types 
n 21 (1) adding tasks possibly use Console.readline 
n 22 (1) extend ls to use date priority etc 
n 23 (1) multiuser 
n 24 (1) descriptions 
n 25 (1) batch operations using stars and plus or minus 
n 26 (1) group by company 
n 27 (1) arbitrary columns 
n 28 (1) group mechanism to create a new parent task for some 
n 29 (1) ranges to determine task ids 
n 30 (1) reporting based on what I did today or yesterday 
n 31 (1) refactor all properties to live in a properties object with name desc order 
n 6 (11) moving tasks 
n 7 (11) removing tasks 
n 8 (11) orphaning tasks 
n 9 (11) confirm removing based on child size 

*/

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.droidski.taskdb.TaskDB;
import com.droidski.taskdb.Util;
import com.droidski.taskdb.model.History;
import com.droidski.taskdb.model.Status;
import com.droidski.taskdb.model.Task;
import com.droidski.taskdb.web.TaskDBServer;

public class Main extends MainCLI {
	
	static TaskDB taskDB;

	public static void main(String[] args) throws Exception {
		Main main = new Main(args);
		if ( main.execute() ) {
			System.exit(0);
		} else {
			System.exit(1);
		}
	}
	
	public Main(String[] args) throws Exception {
		super(args);
	}
	
	private void loadDB() throws Exception {
		File file = TaskDB.findFile();
		loadDB(file);
	}

	private void loadDB(File file) throws Exception {
		if ( taskDB != null ) return;
		if ( file == null ) {
			file = TaskDB.findFile();
		}
		if ( !file.exists() ) {
			die("Cannot find a .taskdb");
		} else {
			taskDB = TaskDB.load(file);
			taskDB.setVerbose( cli.isVerbose() );
		}
	}

	@Command(name="server", 
			description="starts the task webserver",
			group="web"
				)
	public boolean server() {
		try {
			TaskDBServer server = new TaskDBServer(cli);
			server.start();
			return true;
		} catch (Exception e) {
			err(e.getMessage());
			return false;
		}
	}
	
	@Command(name="init", 
			description="creates a new .taskdb database in the current directory",
			help="Usage: init\n\n\tcreates a new .taskdb\n"
				)
	public TaskDB init() throws Exception {
		File file = TaskDB.findFile();
		if (!file.exists()) {
			TaskDB db = TaskDB.init(file);
			out("taskdb initialised successfully.");
			return db;
		} else {
			die(".taskdb already exists");
			return null;
		}
	}

	@Command(name="test", 
			description="evil tests",
			group="test"
				)
	public boolean test() throws Exception {
		
		for ( String s : cli.args ) {
			System.out.println(s);
		}
		
		return true;
		
	}

	@Command(name="time", 
			description="time to load/save",
				group="test"
				)
	public void time() throws Exception {
		long start = System.currentTimeMillis();
		loadDB();
		long loadTime = System.currentTimeMillis() - start;
		long start2 = System.currentTimeMillis();
		taskDB.save();
		long saveTime = System.currentTimeMillis() - start2;
		System.out.println(taskDB.getSize() + " loaded in " + loadTime + ", saved in " + saveTime);
	}

	@Command(name="priority", 
			description="sets a priority on the task",
			help="priority <<value>> <<id>> <<id>> <<id>>",
			group="admin")
	public void priority() throws Exception {
		loadDB();
		String priority = getCommandValue();
		String[] ids = cli.getAllValuesFrom(2).split(" ");
		for ( String id : ids ) {
			Task task = getTaskOrDie(id);
			task.setPriority(priority);
		}
		taskDB.save();
	}

	@Command(name="category", description="sets the task to be in a category", group="admin",
			help="category <<value>> <<id>> <<id>> <<id>>"
	)
	public void category() throws Exception {
		loadDB();
		String priority = getCommandValue();
		String[] ids = cli.getAllValuesFrom(2).split(" ");
		for ( String id : ids ) {
			Task task = getTaskOrDie(id);
			task.setCategory(priority);
		}
		taskDB.save();
	}

	@Command(name="project", description="allocates the task to a project", group="admin",
			help="project <<value>> <<id>> <<id>> <<id>>")
	public void project() throws Exception {
		loadDB();
		String priority = getCommandValue();
		String[] ids = cli.getAllValuesFrom(2).split(" ");
		for ( String id : ids ) {
			Task task = getTaskOrDie(id);
			task.setProject(priority);
		}
		taskDB.save();
	}
	
	@Command(name="ls", description="displays the tasks as a list",
			help="Usage: ls (id) (-d depth) ", group="admin")
	public void ls() throws Exception {
		loadDB();
		List<String> list = new ArrayList<String>();
		int maxDepth = cli.getIntOrDefault("-d", -1);
		if ( maxDepth > 0 ) maxDepth++;
		String cval = getCommandValue();
		if ( cval != null && !cval.equals("-d") ) {
			try {
				int id = Integer.parseInt(cval);
				Task t = getTaskOrDie(cval);
				list.add(t.getId());
			} catch (Exception e) {
				list.add(taskDB.getCurrentRootTask().getId());
			}
		} else {
			list.add(taskDB.getCurrentRootTask().getId());
		}
		Report.writeOutTree(true, 0, maxDepth, list);
		
	}

	@Command(name="export", description="exports the current task db in the format specified", group="report")
	public void export() throws Exception {
		loadDB();
		Report.debugTree();
	}
	
	@Command(name="status", description="shows status of the current task", group="report")
	public void status() throws Exception {
		loadDB();
		String id = getCommandValue();
		Task task = getTaskOrDie(id);
		System.out.println(task.getStatus().toLongString());
	}

	@Command(name="pause", description="pauses the current task", group="admin")
	public void pause() throws Exception {
		loadDB();
		if ( taskDB.getActiveTask() != null ) {
			String comment = cli.getAllValuesFrom(1);
			taskDB.getActiveTask().pause(comment);
			taskDB.save();
		} else {
			die("No active task.");
		}
	}
	
	@Command(name="start", description="starts work on a task, pausing the previous task", group="admin")
	public void start() throws Exception {
		loadDB();
		Task task = getTaskOrDie(getCommandValue());
		String comment = cli.getAllValuesFrom(1);
		task.start(comment);
		taskDB.save();
	}

	@Command(name="complete", description="starts work on a task, pausing the previous task", group="admin")
	public void complete() throws Exception {
		loadDB();
		Task task = getTaskOrDie(getCommandValue());
		String comment = cli.getAllValuesFrom(1);
		task.complete(comment);
		taskDB.save();
	}

	// taskdb setroot ID
	@Command(name="root", description="sets the current root task", group="admin")
	public void root() throws Exception {
		loadDB();
		try {
			Task newRoot;
			String commandValue = getCommandValue();
			if ( commandValue == null || "".equals(commandValue)) {
				out("Current root is " + taskDB.getCurrentRootTask().getId());
				System.exit(0);
			}
			if ( commandValue.equals("/") || commandValue.equals("-")) {
				newRoot = taskDB.getBaseTask();
			} else if ( commandValue.equals("..") ) {
				newRoot = taskDB.getCurrentRootTask().getParent();
				if ( newRoot == null ) {
					die("Current root (" + taskDB.getCurrentRootTask().getId() + ") has no parent, cannot '..'");
				}
			} else {
				String id = getCommandValue();
				newRoot = taskDB.findTaskById(id);
				if ( newRoot == null ) {
					die("Task '" + id + "' not found.");
				}
			}
			
			taskDB.setCurrentRootTask(newRoot);
			taskDB.save();
			
		} catch (NumberFormatException e ) {
			die("Not a valid id.");
		}
	}
	
	@Command(name="rename", description="renames a task", group="admin")
	public void rename() throws Exception {
		loadDB();
		Task task = getTaskOrDie(getCommandValue());
		String name = cli.getAllValuesFrom(2);
		if ( name == null || name.equals("") ) {
			die("Invalid name");
		} else {
			task.setName(name);
			taskDB.save();
		}
		
	}

	@Command(name="mv", description="changes a task heirarchy", group="admin")
	public void move() throws Exception {
		loadDB();
		String allIds = cli.getAllValuesFrom(1);
		String[] ids = allIds.split(" ");
		Task parent = getTaskOrDie(ids[ids.length-1]);
		for( int i=0; i<ids.length-1; i++) {
			Task task = getTaskOrDie(ids[i]);
			parent.addTask(task);
		}
		taskDB.save();
	}
	
	@Command(name="rm", description="removes a task", group="admin")
	public void rm() throws Exception {
		loadDB();
		Task task = getTaskOrDie(getCommandValue());
		taskDB.deleteTask(task);
		taskDB.save();
	}

	@Command(name="history", description="displays activity on the database", group="report")
	public void history() throws Exception {
		loadDB();
		Task task = getTaskOrDie(getCommandValue());
		List<History> history = task.getHistory();
		for ( History h : history ) {
			out(Util.formatDate(h.getTime()) + " " + h.getStatus().toLongString());
		}
	}
		
	@Command(name="info", description="displays information on this .taskdb database", group="report")
	public void info() throws Exception {
		loadDB();
		int size = taskDB.getSize();
		long lastModified = taskDB.getDateLastModified();
		long dateCreated = taskDB.getDateCreated();
		File file = taskDB.getFile();
		Task root = taskDB.getBaseTask();
		Task currentRoot = taskDB.getCurrentRootTask();
		
		out(".taskdb " + taskDB.getVersion());
		out("location     : " + file);
		out("size         : " + size + " tasks");
		out("created      : " + Util.formatDate(dateCreated));
		out("modified     : " + Util.formatDate(lastModified));
		out("root         : " + root.getId() + " '" + root.getName() + "'");
		out("current root : " + currentRoot.getId() + " '" + currentRoot.getName() + "'");
	}

	@Command(name="search", description="searches tasks for the search phrase", group="report")
	public void search() throws Exception {
		loadDB();
		String searchFor = getCommandValue().toLowerCase().trim();
		if ( searchFor == null ) {
			die("Usage: " + getExecutableName() + " search <<SEARCH_TERM>>");
		}
		List<Task> hits = taskDB.searchForTasks(searchFor);
		if ( hits.size() > 0 ) {
			Report.writeOutFlat(hits);
		} else {
			out("No matches for '" + getCommandValue() + "'");
		}
	}
	
	@Command(name="add", description="creates a new task",
			help="Usage: add [the task name] ( -p parent_id to attach to )", group="admin")
	public void add() throws Exception {
		loadDB();
		String name = cli.getAllValuesFrom(1);
		if ( name == null ) {
			die("Usage: " + getExecutableName() + " add <<name>> (-d <<description>> -p <<parent_id>>)");
		}
		String desc = cli.getValue("-d");
		String parent = cli.getValue("-p");
		
		Task parentTask = null;
		if ( parent != null && !"".equals(parent) ) {
			try {
				String id = parent;
				parentTask = taskDB.findTaskById(id);
				if ( parentTask == null ) {
					die("Parent id '" + parent + "' not found.");
				}
			} catch (NumberFormatException e) {
				die("Parent id '" + parent + "' is invalid.");
			}
			
		} else {
			parentTask = taskDB.getCurrentRootTask();
		}
		
		Task task = taskDB.createTask(name);
		task.setDescription(desc);
		parentTask.addTask(task);
		taskDB.save();
		
	}
	
	private Task getTaskOrDie(String idStr) {
		if ( idStr == null ) {
			die("Task id not specified");
		} else {
			try {
				String id = idStr;
				Task task = taskDB.findTaskById(id);
				if ( task == null ) {
					die("Task '" + idStr + "' does not exist.");
				} else {
					return task;
				}
			} catch (Exception e) {
				die("Task id " + idStr + " is invalid.");
			}
		}
		return null;
	}
	
	private Task getTaskOrDieAndSearch(String idStr) {
		if ( idStr == null ) {
			die("Task id not specified");
		} else {
			try {
				String id = idStr;
				Task task = taskDB.findTaskById(id);
				this.search();
				if ( task == null ) {
					die("Task '" + idStr + "' does not exist.");
				} else {
					return task;
				}
			} catch (Exception e) {
				die("Task id " + idStr + " is invalid.");
			}
		}
		return null;
	}
	
	
	static class Report {

		public static void writeOutTree(boolean showHeader, int currentDepth, int maxDepth, List<String> tasks) {

			if ( currentDepth == maxDepth ) return;
			
			StringBuffer sb = new StringBuffer();
			String header = null;
			
			if ( showHeader ) {
				sb.append( Util.rpad("status", " ", 7) );
				sb.append( Util.rpad("id", " ", 4) );
				sb.append( Util.rpad("parent", " ", 7) );
				sb.append( Util.rpad("name", " ", 50) );
				sb.append( Util.rpad("priority", " ", 10) );
				sb.append( Util.rpad("project", " ", 10) );
				sb.append( Util.rpad("category", " ", 10) );
				sb.append( Util.rpad("today", " ", 6) );
				sb.append( Util.rpad("week", " ", 6) );
				sb.append( Util.rpad("month", " ", 6) );
				sb.append( Util.rpad("total", " ", 6) );
			
				header = sb.toString();
				out(Util.rpad("-", header.length()+1));
				out(header);
				out(Util.rpad("-", header.length()+1));

			}

			for ( int i=0; i<tasks.size(); i++) {
				Task task = taskDB.findTaskById(tasks.get(i));
				sb = new StringBuffer();
				sb.append( Util.rpad(task.getStatus().toString(), " ", 7) );
				sb.append( Util.rpad(task.getId(), " ", 4) );
				sb.append( Util.rpad(task.getParentId(), " ", 7) );
				
				String name = Util.rpad(" ", currentDepth) + task.getName();
				name = Util.rpad(name, " ", 50);
				sb.append( name );
				
				sb.append( Util.rpad(task.getPriority(), " ", 10) );
				sb.append( Util.rpad(task.getProject(), " ", 10) );
				sb.append( Util.rpad(task.getCategory(), " ", 10) );
				sb.append( Util.rpad( Util.formatTimeSpent(task.getTimeAccumulatedSince(Util.getTimeToday())), " ", 6) );
				sb.append( Util.rpad( Util.formatTimeSpent(task.getTimeAccumulatedSince(Util.getTimeWeek())), " ", 6) );
				sb.append( Util.rpad( Util.formatTimeSpent(task.getTimeAccumulatedSince(Util.getTimeMonth())), " ", 6) );
				sb.append( Util.rpad( Util.formatTimeSpent(task.getTimeAccumulated()), " ", 6) );
				if ( task.getStatus() == Status.ACTIVE ) {
					sb.append("*");
				}
				out(sb.toString());
				
				if ( task.getChildren().size() > 0 ) {
					writeOutTree(false, currentDepth+1, maxDepth, task.getChildren());
				}
			}
			if ( showHeader ) {
				out(Util.rpad("-", header.length()+1));
			}

		}

		public static void writeOutFlat(List<Task> tasks) {
			
			StringBuffer sb = new StringBuffer();
			sb.append( Util.rpad("status", " ", 7) );
			sb.append( Util.rpad("id", " ", 4) );
			sb.append( Util.rpad("parent", " ", 7) );
			sb.append( Util.rpad("name", " ", 40) );
			sb.append( Util.rpad("priority", " ", 10) );
			sb.append( Util.rpad("project", " ", 10) );
			sb.append( Util.rpad("category", " ", 10) );
			sb.append( Util.rpad("time", " ", 10) );
			
			String header = sb.toString();
			out(Util.rpad("-", header.length()+1));
			out(header);
			out(Util.rpad("-", header.length()+1));
			
			for ( int i=0; i<tasks.size(); i++) {
				Task task = tasks.get(i);
				sb = new StringBuffer();
				sb.append( Util.rpad(task.getStatus().toString(), " ", 7) );
				sb.append( Util.rpad(task.getId(), " ", 4) );
				sb.append( Util.rpad(task.getParentId(), " ", 7) );
				sb.append( Util.rpad(task.getName(), " ", 40) );
				sb.append( Util.rpad(task.getPriority(), " ", 10) );
				sb.append( Util.rpad(task.getProject(), " ", 10) );
				sb.append( Util.rpad(task.getCategory(), " ", 10) );
				sb.append( Util.rpad( Util.formatTimeSpent(task.getTimeAccumulated()), " ", 10) );
				if ( task.getStatus() == Status.ACTIVE ) {
					sb.append("*");
				}
				out(sb.toString());
			}
			out(Util.rpad("-", header.length()+1));
			
		}
		
		public static void debugTree() {
			debugTree(taskDB.getBaseTask(), 0);
		}

		public static void debugTree(Task task, int indent) {
			String indentStr = " ";
			for (int index = 0; index < indent; index++) {
				indentStr += " ";
			}

			if ( task.getParent() != null ) {
				System.out.println(task.getStatus().toString() 
						+ " " 
						+ indentStr 
						+ task.getId()
						+ " "
						+ "(" + task.getParent().getId() + ")"
						+ " " 
						+ task.getName()
						+ " " 
						+ Util.formatTimeSpent(task.getTimeAccumulated()) );
			} else {
				System.out.println(task.getStatus().toString() 
						+ " " 
						+ indentStr 
						+ task.getId()
						+ " "
						+ "( )"
						+ " " 
						+ task.getName()
						+ " " 
						+ Util.formatTimeSpent(task.getTimeAccumulated()) );
				
			}
				
			if (task.getChildren().size() > 0) {
				for (String childId : task.getChildren()) {
					Task child = taskDB.findTaskById(childId);
					debugTree(child, indent + 1);
				}
			}
		}

		public static int getIDWidth() {
			int length = 0;
			for ( Task task : taskDB.getTasks().values() ) {
				int thisLength = ("" +  task.getId()).length();
				length = thisLength > length ? thisLength : length;
			}
			return length;
		}

		public int getTimeSpentWidth() {
			int length = 0;
			for ( Task task : taskDB.getTasks().values() ) {
				int thisLength = Util.formatTimeSpent(task.getTimeActual()).length();
				length = thisLength > length ? thisLength : length;
			}
			return length;
		}

		public static int getNameWidth() {
			int length = 0;
			for ( Task task : taskDB.getTasks().values() ) {
				int thisLength = ("" +  task.getName()).length();
				length = thisLength > length ? thisLength : length;
			}
			return length;
		}

		public static int getDescriptionWidth() {
			int length = 0;
			for ( Task task : taskDB.getTasks().values() ) {
				int thisLength = ("" +  task.getDescription()).length();
				length = thisLength > length ? thisLength : length;
			}
			return length;
		}
		
	}

	@Override
	public String getExecutableName() {
		return "task";
	}
	
}
